/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
    This file was generated by VMB using these parameters:

        Benchmarks Class:     $state_name
        Bench Setup:          $state_setup
        Bench Method:         $method_name -> $method_rettype
        Parameter Set:        #$fix_id $fixture
        Bench Full Name:      $bench_name
        WarmUp Iterations:    $wi
        Measure Iterations:   $mi
        Iteration Time (sec): $it
        Warmup Time (sec):    $wt
        Fast Iterations:      $fi
        GC pause (ms):        $gc (N/A)
*/

'use strict';

$imports

// params
let WI = $wi;
let MI = $mi;
let IT = $it;
let WT = $wt;
let FI = $fi;

const MAX_LOOP_COUNT = 10_000_000_000;
const MS2NS = 1_000_000;
const S2MS = 1_000;

class Consumer {
    static x1 = 0x41c64e6d;
    static x2 = 0xd431;
    static x3 = 1;

    static boola = false;
    static boolb = true;
    static chara = 'X';
    static charb = 'Y';
    static inta = 24;
    static intb = 53;
    static floata = 24.0;
    static floatb = 53.0;

    static localObj = new Object();
    static localObjs = [new Object()];
    static pseudorand = Date.now();

    static consumeBool(boolc) {
        if (boolc === this.boola && boolc === this.boolb) {
            throw new Error();
        }
    };

    static consumeChar(charc) {
        if (charc === this.chara && charc === this.charb) {
            throw new Error();
        }
    };

    static consumeInt(intc) {
        if (intc === this.inta && intc === this.intb) {
            throw new Error();
        }
    };

    static consumeFloat(floatc) {
        if (floatc === this.floata && floatc === this.floatb) {
            throw new Error();
        }
    };

    static consumeObj(obj) {
        this.pseudorand = (this.pseudorand * this.x1 + this.x2);
        if ((this.pseudorand & this.x3) === 0) {
            this.x3 = (this.x3 << 1) + 0xad;
            this.localObj = obj;
        }
    };

    static consumeObjs(objs) {
        this.pseudorand = (this.pseudorand * this.x1 + this.x2);
        if ((this.pseudorand & this.x3) === 0) {
            this.x3 = (this.x3 << 1) + 0xad;
            this.localObjs = objs;
        }
    };
};

function log(msg) {
    console.log(msg);
}

var loopCount1;
var totalOps;
var totalMs;
var iter;


let penv = process.env;
let stsVm = require(penv.MODULE_PATH + '/ets_interop_js_napi.node');
const stsRT = stsVm.createRuntime({
    'boot-panda-files':
        penv.ARK_ETS_STDLIB_PATH + ':' + penv.ARK_ETS_INTEROP_JS_GTEST_ABC_PATH,
    'panda-files': penv.ARK_ETS_INTEROP_JS_GTEST_ABC_PATH,
    'gc-trigger-type': 'heap-trigger',
    'compiler-enable-jit': 'false',
    'run-gc-in-place': 'true',
});
if (!stsRT) {
    console.error('Failed to create ETS runtime');
    return 1;
}
const State = stsVm.getClass('L$state_name;');
let bench = new State();
$state_params
$state_setup

function tune() {
    let iterMs = 1 * S2MS;
    let loopMs = 0;
    let loopCount = 1;
    while (loopMs < iterMs && loopCount < MAX_LOOP_COUNT) {
        loopCount = loopCount * 2;
        let start = Date.now();
        for (let i = 0; i < loopCount; i++) {
            $method_call
        }
        loopMs = Date.now() - start;
    }
    loopCount1 = loopCount * iterMs / loopMs >> 0;
    if (loopCount1 == 0) loopCount1++;
    log('Tuning: ' +  loopCount + ' ops, ' + loopMs*MS2NS/loopCount + ' ns/op => '
        + loopCount1 + ' reps');
}

function runIters(phase, count, time) {
    let iterMs = time * S2MS;
    totalOps = 0;
    // make sure zero is encoded as double to avoid deoptimization on overflow later
    totalMs = 1/Infinity;
    for (let k = 0; k < count; k++, iter++) {
        let ops = 0;
        let elapsedMs = 0;
        let start = Date.now();
        while (elapsedMs < iterMs) {
            for (let i = 0; i < loopCount1; i++) {
                $method_call
            }
            elapsedMs = Date.now() - start;
            ops += loopCount1;
        }
        totalOps += ops;
        totalMs += elapsedMs;
        log(phase + ' ' + iter + ':' + ops + ' ops, ' + elapsedMs*MS2NS/ops + ' ns/op');
    }
}

log('Startup execution started: ' + Date.now() * MS2NS);
tune();
if (WI > 0) {
    iter = 1;
    // Re-entering runIters in warmup loop to allow profiler complete the method.
    // Possible deoptimizations and recompilations is done in warmup instead of measure phase.
    for (let wi = 0; wi < WI; ++wi) {
        runIters('Warmup', 1, WT);
    }
}
iter = 1;
var measure_iters = MI >> 0; // make sure it has int type
runIters('Iter', measure_iters, IT);
log('Benchmark result: $bench_name ' + totalMs*MS2NS/totalOps);
Consumer.consumeObj(bench);
